<?php
/**
 * Created by PhpStorm.
 * User: Inhere
 * Date: 2015/1/28
 * Use : ...
 * File: ModuleManager.php
 */

namespace ulue\core\module;

use Ioc,Ulue,Debug;
use ulue\core\abstracts\AbstractCore;

class ModuleManager extends AbstractCore
{
    protected $rawConfig   = [];

    protected $loaded      = [];

    protected $loadedNames = [];

    public $suffix = [
        'module'       => 'Module'          // 默认模块class名称后缀
        ,'controller'  => 'Controller'      // 默认控制器类名称后缀
        ,'actionClass' => 'Action'          // 默认控制器动作绑定类名称后缀
        ,'action'      => 'Action'          // 默认控制器动作方法名称后缀
        ,'model'       => ''                // 默认模型后缀 suffix--后缀 Model
        // ,'view'        => "html"            // 视图(模板)文件后缀
        // ,'layout'      => "php"            // 布局(模板)文件后缀
    ];

    public $current        = null;

    public $currentPath    = null;

    /**
     * 当前模块的配置
     * @var array
     */
    public $currentConfig  = [];

    public $controller     = null;

    public $controllerPath = null;

    public $action         = null;

    public function __construct(array $modules)
    {
        $this->rawConfig = $modules;

        $this->loaded    = $this->handle($modules);

        $defaultModule   = Ioc::get('app')->defaultModule;

        // 默认模块，不用在配置中显式定义
        if ($defaultModule && !isset($this->loaded[$defaultModule]) ) {
            $this->loaded[$defaultModule] = [];
            $this->loadedNames[] = $defaultModule;
        }
    }

    /**
     * 整理 模块信息
     * @param  array $modulesConfig 启用并设置模块的配置数组 (at config.php)
     * 'modules' => [
     *       'admin', 'home', // 启用模块
     *       'builder'  => [
     *           // 若配置模块入口类，则将根据此配置来查找模块，即 模块不一定必须在 src/ 目录下
     *           // 注意：自定义特殊模块目录时，请先配置好命名空间对应路径引入 -- 设置'path' ，也可在 'namespaceImport' 项设置
     *           'class'                => 'builder\BuilderModule',
     *
     *           // 属性设置
     *           'accessPwd'            => '12345',                  #独立访问密码
     *           'ipFilters'            => ['127.0.0.1','::1'],      #允许访问的ip
     *           'protected'            => 'admin,shop,book',        #受保护模块，删除模块需要密码
     *           'delete_pwd'           => 'ok123',                  #删除模块密码
     *
     *            // 子模块
     *           'modules' => [
     *                  'submodule1',
     *                  'submodule2' => ['aa'=>343434],
     *                  ]
     *           ],
     *   ]
     * @return void
     */
    protected function handle(array $modulesConfig)
    {
        $loaded    = [];

        foreach ($modulesConfig as $key => $value) {
            // 仅一个名称
            if (is_string($value)) {
                $this->loadedNames[] = $name = trim($value);
                $loaded[$name] = [];
            } elseif (is_array($value)) {
                $this->loadedNames[] = $name = trim($key);
                $loaded[$name] = $value;

                if ( !empty($value['modules']) ) {
                    $subMoudles    = $value['modules'];
                    $loaded[$name]['modules'] = $this->handle($subMoudles);
                }
            }
        }

        return $loaded;
    }

    /**
     * 根据请求模块 载入当前模块信息
     * @param  string $routeModule
     */
    public function loadCurrent($routeModule)
    {
        $routeModule   = trim($routeModule, '/ ');
        $config        = $this->find($routeModule);
        $this->current = $routeModule;

        if ( $config === null ) {
            throw new \DNotFoundException("模块[ $name ]不存在");
        }

        $this->currentConfig = $config;
        $this->currentPath   = $this->_findPathByConfig($routeModule, $config);
    }

    public function getCurrentName()
    {
        return self::className($this->current);
    }

    /**
     * 存在模块, 必须按层次查找
     * @param  string  $routeModule e.g. 模块：admin 子模块：admin/submodule
     * @return boolean
     */
    public function exists($routeModule)
    {
        return $this->find($routeModule) !== null;
    }

    /**
     * 仅仅是判断是否存在模块名
     * @param  string  $name
     * @return boolean
     */
    public function has($name)
    {
        return in_array($name, $this->loadedNames);
    }

    /**
     * 判断是否存在顶级模块
     * @param  string  $name
     * @return boolean
     */
    public function fetchTop($name)
    {
        return isset($this->loaded[$name]) ? $this->loaded[$name] : false;
    }

    public function find($name)
    {
        $name = trim($name, '/ ');

        if ( strpos($name, '/') ) {
            $names = array_values(array_filter(explode('/', $name)));

            if ( isset($names[1]) ) {
                $parent = array_shift($names);

                return $this->findSubmodule( implode('/', $names), $parent, $this->loaded);
            }
        }

        if (isset($this->loaded[$name])) {
            return $this->loaded[$name];
        }

        return null;
    }

    /**
     * 子模块查找
     * @param  string $name   要查找的子模块名
     * @param  string $parent 父级模块名
     * @param  mixed  $loaded 模块配置
     * @return mixed
     */
    protected function findSubmodule($name, $parent, $loaded)
    {
        $name = trim($name, '/ ');

        if ( !is_array($loaded) || !isset($loaded[$parent]) ) {
            return null;
        }

        // 当前父模块配置数据 是否有子模块配置
        if ( !isset($loaded[$parent]['modules'])) {
            return null;
        }

        // 切换载入当前父模块的子模块配置数据
        $loaded = $loaded[$parent]['modules'];

        // 有无子模块
        if (isset($loaded[$name])) {
            return $loaded[$name];
        }

        // 再下一层查找
        if (strpos($name, '/')) {
            $names = array_values(array_filter(explode('/', $name)));

            if ( isset($names[1]) ) {
                $parent = array_shift($names);

                return $this->findSubmodule( implode('/', $names), $parent, $loaded);
            }
        }

        return null;
    }

    /**
     * 查找模块目录
     * @param  string $name
     * @return string
     */
    public function findPath($name)
    {
        $name = trim($name, '/ ');

        if ( ( $config = $this->find($name) )!== null ) {
            return $this->_findPathByConfig($name, $config);
        }

        throw new \DNotFoundException("模块[ $name ]不存在");
    }

    /**
     * 查找模块所在路径
     * @param  string     $name
     * @param  array      $config
     * @return string
     */
    protected function _findPathByConfig($name, array $config)
    {
        if ( isset($config['class']) ) {
            return Ulue::resolveNamespace($config['class']);
        }

        $basePath = Ulue::$app->basePath;
        $names    = array_values(array_filter(explode('/', $name)));
        $dir      = $name;

        // 子模块
        if ( isset($names[1]) ) {
            $top = array_shift($names);
            $dir = $top . '/modules/' . implode('/modules/', $names);
        }

        $path = $basePath . '/' . $dir;

        if (!is_dir($path)) {
            throw new \DNotFoundException("模块[ $name ]的目录[ $path ]不存在");
        }

        return $path;
    }

    /**
     * 检查当前模块是否存在控制器
     * 控制器最多只允许有一级子目录
     * @param  string  $routeCtrl 控制器名 e.g. post post/mypost(子目录)
     * @return boolean
     */
    public function hasController($routeCtrl)
    {
        return $this->findController($routeCtrl) ? true:false;
    }

    /**
     * 查找当前模块的控制器
     * @param  string $routeCtrl
     * @return string|null
     */
    public function findController($routeCtrl)
    {
        if (!$routeCtrl) {
            return null;
        }

        if ($modulePath = $this->currentPath) {
            $routeCtrl = trim($routeCtrl, '/ ');

            if ( strpos($routeCtrl, '/') ) {
                list($dir, $routeCtrl) = explode('/', $routeCtrl);
                $controller = $dir . '/' . ucfirst($routeCtrl);
            } else {
                $controller = ucfirst($routeCtrl);
            }

            $controllerFile = $modulePath . '/controllers/' . $controller . 'Controller.php';

            return file_exists($controllerFile) ? $controllerFile : null;
        }

        throw new \DInvalidArgumentException("没有模块信息，请先查找设置模块！");
    }

    /**
     * Instantiate the application module  activeModule
     * @param string|array $route
     * @throws TheAppException
     * @throws \DRequestException
     * @throws \DRuntimeException
     * @return Module
     */
    public function activeModule($route)
    {
        if ( !$route ) {
            throw new \DInvalidArgumentException("没有路由信息，无法匹配解析资源！");
        }

        Debug::trace("i 准备激活并进入应用模块",[ 'route' => $route ]);

        $module = $this->findModule( is_string($route) ? $route : $route['module'] );

        $options = $this->currentConfig;

        // 自定义模块类
        if (!empty($options['class'])) {
            $moduleClass = $options['class'];
            unset($options['class']);
        } else {
            $moduleClass = self::className($routeModule) . $this->suffix['module'];
            $namespace   = str_replace('/', '\\', $routeModule);
            $moduleClass = $namespace .'\\'.ucfirst($moduleClass);

            Ioc::$box->loader->setNs($namespace, $this->currentPath);
        }

        /**
         * @var $objModule \ulue\core\module\AbstractModule
         */
        $objModule       = new $moduleClass( $options);

        if (! $objModule instanceof AbstractModule) {
            throw new \DRuntimeException(sprintf('应用模块 [%s] 入口控制类 [%s] 必须继承自[\ulue\core\module\AbstractModule]!!', $routeModule, $moduleClass));
        }

        $objModule->setName($routeModule)->setRoute('/'.$routeModule)->setPath($this->currentPath);

        // 查找父级 模块
        if ( strpos($routeModule, '/') ) {
            $this->_findParent(dirname($routeModule), $objModule);
        }

        return $objModule;
    }

    protected function findModule($route, AbstractModule $parent=null)
    {
        if ( strpos($route, '/') ) {
            list($name, $route) = explode('/', $route, 2);
        } else {
            $name = $route;
        }

        // 查找顶级模块
        if ( !$parent ) {
            if ( ! $options = $this->fetchTop($name) ) {
                throw new \DRequestException("访问不存在顶级模块[{$name}]");
            }
        }

    }

    /**
     * 查找父级 模块
     * @param  string         $routeModule
     * @param  AbstractModule $currentModule
     * @return void
     */
    protected function _findParent($routeModule, AbstractModule $module)
    {
        $options   = $this->find($routeModule);
        $hasParent = strpos($routeModule, '/');

        // 自定义模块类
        if (!empty($options['class'])) {
            $moduleClass = $options['class'];
            unset($options['class']);
        } else {
            $moduleName  = self::className($routeModule);
            $namespace   = str_replace('/', '\\', $routeModule);
            $moduleClass = $namespace .'\\'. ucfirst($moduleName) . $this->suffix['module'];

            if ($hasParent) {
                Ioc::$box->loader->setNs($namespace, preg_replace("/\/modules\/$moduleName$/i", '', $module->path) );
            }
        }

        /**
         * @var $objModule \ulue\core\module\AbstractModule
         */
        $objModule     = new $moduleClass( $options);

        if (! $objModule instanceof AbstractModule) {
            throw new \DRuntimeException(sprintf('应用模块 [%s] 入口控制类 [%s] 必须继承自[\ulue\core\module\AbstractModule]!!', $routeModule, $moduleClass));
        }

        $objModule->setName($routeModule)->setRoute('/'.$routeModule);

        $module->parent = $objModule;

        // 继续 递归查找父级 模块
        if ( $hasParent ) {
            $this->_findParent(dirname($routeModule), $objModule);
        }
    }

    public function activeController(AbstractModule $module)
    {
        if ( ! $routeModule = $this->current ) {
            throw new \DInvalidArgumentException("没有模块信息，请先查找设置模块！");
        }

        $name = $this->controller ? : $module->defaultController;
        $name = trim($name,'/ ');

        Debug::trace("i 根据请求的控制器名 {$name},获取控制器对象并返回",
            "@param \$name = {$name} <br>@internal \$routeModule {$routeModule}"
        );

        // 命名空间跟着模块走，因为模块不一定在 src 目录
        $app            = Ulue::$app;
        $dirname        = Ioc::$box->pathManager->dir('controllers');
        $namespace      = $module::spaceName() . "\\$dirname";


        if ( strpos($name, '/') ) {
            $filename = preg_replace("/\/\w+$/i", '\\', $name) . ucfirst(self::className($name)).$this->suffix['controller'];
        } else {
            $filename = ucfirst($name).$this->suffix['controller'];
        }

        /**
         * 访问默认模块下的控制器，可省略模块路由
         */
        $controllerUrl = ($routeModule == $app->defaultModule ? '' : '/'.$routeModule).'/'.$name;

        // 路由信息中没有控制器， 在此进行控制器文件检查(ps:使用默认控制器时)
        if (!$fullPath = $this->controllerPath) {
            $fullPath  = $module->path. "/{$dirname}/{$filename}.php";

            if (!file_exists($fullPath) ) {
                throw new \DRuntimeException(Ulue::tl('Requested file [{file}] does not exist!',['{file}'=>$fullPath]));
            }
        }

        // 组装含命名空间的 完整控制器类名 admin\controllers\DefaultController
        $fullClass = $namespace . '\\' . $filename;
        $parentClass = '\ulue\core\controller\AbstractWebController';
        // d( $this->currentPath,$this->controllerPath,$name,$namespace,$filename,$fullClass);

        /**
         * 实例化控制类
         * @var $controller \ulue\core\controller\AbstractWebController
         */
        $controller  = new $fullClass();

        # 必须继承自某个基类
        if ( !is_subclass_of($controller, $parentClass) ) {
            throw new \DRuntimeException(Ulue::tl(
                'Class [{class}] must directly or indirectly inherits a base class "{parentClass}"',
                [
                    '{class}' => $fullClass,
                    '{parentClass}' => $parentClass
                ] )
            );
        }

        $controller->setPath( dirname($fullPath) )->setName($name)->setRoute($controllerUrl);

        unset($app,$filename,$dirname,$fullClass,$fullPath);

        return $controller;
    }

    public function activeAction($action)
    {
        # code...
    }

    public function getRawConfig()
    {
        return $this->rawConfig;
    }

    public function setLoaded($value)
    {
        if (is_array($value)) {
            $this->loaded = $value;
        }
    }

    public function getLoaded()
    {
        return $this->loaded;
    }

    public function getLoadedNames()
    {
        return $this->loadedNames;
    }

    /**
     * 设置模块信息
     * @param  string  $name e.g. 模块：admin 子模块：admin/submodule
     * @return boolean
     */
    public function setModule($name, $key, $value=null)
    {
        if ( $this->exists($name) ) {

        }

        return $this->loaded;
    }

    public function __get($name)
    {
        if ($config = $this->find($name) ) {
            return $config;
        }

        return parent::__get($name);
    }

}//end class ModuleManager